<?php

namespace Helpers;

class TransitClient
{
    ///////////////////////////////////////////////
    /// JsonDesc
    ///////////////////////////////////////////////
//    const To = 'to';
//    const ToCenterServer = 'cs';
//    const ToAppServer = 'app';
//    const ToServerTypeAll = 'tpAll';
//    const ToServerTypeRand = 'tpRand';
//    const ToServerWithId = 'idSvr';
//    const ToAllServer = 'all';

    ///////////////////////////////////////////////
//    const ServerType = 'tp';
//    const TypeCenterServer = 1;
//    const TypeLogServer = 2;
//    const TypeZtyTransit = 3;
//    const TypeGateway = 4;
//    const TypeLoginServer = 5;
//    const TypeMySqlServer = 6;
//    const TypeAppGuWenServer = 7;
//    const TypePbServer = 8;

    ///////////////////////////////////////////////
//    const ServerId = 'id';

    ///////////////////////////////////////////////
//    const Func = 'func';
//    const SchoolDbsId = 'schDbsId';
//    const SchoolId = 'schoolId';
//    const UnitId = 'unitId';

//    const AppType = 'appType';
//    const AppTypeGuWen = 1200;

    ///////////////////////////////////////////////
//    const NeedReply = 'reply';
//    const NeedReplyNo = 0;
//    const NeedReplyYes = 1;

    ////////////////////////////////////////////////
    /// 
    ///////////////////////////////////////////////
//    const ResultOK = 1;
    const TimeoutSecs = 5;

    ////////////////////////////////////////////////
    /// Socket
    ///////////////////////////////////////////////
    const ErrOK = 0;
    const ErrInvalidSocket = 1000;

    const ErrWriteSocket = 1101;
    const ErrWriteJsonDescTooLong = 1102;
    const ErrWriteJsonDataTooLong = 1103;

    const ErrReadSocket = 1201;
    const ErrReadPacketLen = 1202;
    const ErrReadPacketOpCode = 1203;
    const ErrReadPacketCheckCode = 1204;

    ////////////////////////////////////////////////
    /// Packet
    ///////////////////////////////////////////////
    const WEB_SS_REQ = 31001;
    const WEB_SS_REP = 31002;
    const PacketHeaderLen = 16;
    const PacketHeaderCheckCode = 21301;
    const PseudoWebClientUserId = 2100000000;

    const JsonDescMaxSize = 256;
    const JsonDataMaxSize = 102410;

    ////////////////////////////////////////////////////////
    private $socket_;

    function __destruct()
    {
        if ($this->socket_) {
            @socket_shutdown($this->socket_);
            @socket_close($this->socket_);
        }
    }


    public function socketLastError()
    {
        return @socket_last_error($this->socket_);
    }

    public function connect($ip, $port)
    {
        if ($this->socket_)
            return false;

        $this->socket_ = @socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
        if ($this->socket_ === false)
            return false;

        if ($this->socketConnect($ip, $port))
            return true;

        @socket_close($this->socket_);
        $this->socket_ = null;
        return false;
    }

    public function writeAndReadWebSSReq($jsonDesc, $jsonData, &$data, &$result)
    {
        $err = $this->writeWebSSReq(0, $jsonDesc, $jsonData);
        return $err != self::ErrOK ? $err : $this->readWebSSRep($reqIndex, $data, $result);
    }

    public function writeWebSSReq($reqIndex, $jsonDesc, $jsonData)
    {
        if (!$this->socket_)
            return self::ErrInvalidSocket;

        $jsonDescSize = strlen($jsonDesc) + 1;
        if ($jsonDescSize > self::JsonDescMaxSize)
            return self::ErrWriteJsonDescTooLong;
        $contentDesc = pack("La" . self::JsonDescMaxSize, $reqIndex, $jsonDesc);

        $jsonDataSize = strlen($jsonData) + 1;
        if ($jsonDataSize > self::JsonDataMaxSize)
            return self::ErrWriteJsonDataTooLong;
        $contentData = pack("La*x", $jsonDataSize, $jsonData);


        $content = $contentDesc . $contentData;
        $header = self::packHeader(self::WEB_SS_REQ, self::PacketHeaderLen + strlen($content), self::PseudoWebClientUserId);

        return $this->socketWrite($header . $content) ? self::ErrOK : self::ErrWriteSocket;
    }

    public function readWebSSRep(&$reqIndex, &$data, &$result)
    {
        if (!$this->socket_)
            return self::ErrInvalidSocket;

        $packet = $this->socketRead(self::PacketHeaderLen);
        if ($packet === false)
            return self::ErrReadSocket;

        $header = self::unpackHeader($packet);
        if ($header['opCode'] != self::WEB_SS_REP)
            return self::ErrReadPacketOpCode;

        if ($header['checkCode'] != self::PacketHeaderCheckCode)
            return self::ErrReadPacketCheckCode;

        $packet = $this->socketRead($header['len'] - self::PacketHeaderLen);
        if ($packet === false)
            return self::ErrReadSocket;

        $content = unpack('Lresult/LreqIndex/LdataLen/a*data', $packet);

        $reqIndex = $content['reqIndex'];
        $data = $content['data'];
        if (!empty($data))
            $data = substr($data, 0, strlen($data) - 1);

        $result = $content['result'];
        return self::ErrOK;
    }

    private function socketRead($len)
    {
        $result = "";
        while ($len > 0) {
            $r = array($this->socket_);
            $ret = @socket_select($r, $w = null, $e = null, self::TimeoutSecs);
            if (!$ret)
                return false;

            $read = @socket_read($this->socket_, $len);
            if (!$read)
                return false;

            $result .= $read;
            //create by ck 20160811
            $len -= strlen($read);
            //mark by ck 20160811  应减去每次获取到的长度
            //$len -= strlen($result);

        }
        return $result;
    }

    private function socketWrite($packet)
    {
        while (!empty($packet)) {
            $sended = @socket_write($this->socket_, $packet);
            if ($sended === false)
                return false;
            $packet = substr($packet, $sended);
        }
        return true;
    }

    private function socketConnect($ip, $port)
    {
        @socket_set_nonblock($this->socket_);
        @socket_connect($this->socket_, $ip, $port);
        @socket_set_block($this->socket_);

        $w = array($this->socket_);
        return @socket_select($r = null, $w, $e = null, self::TimeoutSecs) == 1;
    }


    private static function packHeader($opCode, $packLen, $userId)
    {
        return pack('LLLSS', $opCode, $packLen, $userId, self::PacketHeaderCheckCode, 0);
    }

    private static function unpackHeader($packet)
    {
        return unpack('LopCode/Llen/LuserId/ScheckCode/SbackId', $packet);
    }
}
